﻿#region imports

using System;
using System.Collections.Generic;
using System.Data.Services;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Web;
using System.Xml;
using Codeplex.ODataTFS.Model.Entities;
using Microsoft.TeamFoundation.Common;
using Microsoft.TeamFoundation.Server;

#endregion

namespace Codeplex.ODataTFS.Model.Serialization
{
	public class TFSAreaPathProxy : TFSBaseProxy
	{
		public TFSAreaPathProxy(Uri uri, ICredentials credentials)
			: base(uri, credentials)
		{
		}

		public IEnumerable<AreaPath> GetAllAreaPaths()
		{
			if (Cache[GetAllAreaPathsKey()] == null)
			{
				Cache[GetAllAreaPathsKey()] = RequestAllAreaPaths();
			}

			return (IEnumerable<AreaPath>) Cache[GetAllAreaPathsKey()];
		}

		public IEnumerable<AreaPath> GetAreaPathsByProject(string projectName)
		{
			if (Cache[GetAreaPathsByProjectKey(projectName)] == null)
			{
				Cache[GetAreaPathsByProjectKey(projectName)] = RequestAllAreaPathsByProject(projectName);
			}

			return (IEnumerable<AreaPath>) Cache[GetAreaPathsByProjectKey(projectName)];
		}

		public IEnumerable<AreaPath> GetSubAreas(string rootAreaName)
		{
			if (Cache[rootAreaName] == null)
			{
				Cache[rootAreaName] = RequestSubAreas(rootAreaName);
			}

			return (IEnumerable<AreaPath>) Cache[rootAreaName];
		}

		private IEnumerable<AreaPath> RequestAllAreaPaths()
		{
			var css = TfsConnection.GetService<ICommonStructureService3>();
			var allStructures = css.ListAllProjects().SelectMany(p => css.ListStructures(p.Uri));
			var areaPathsXml = css.GetNodesXml(allStructures.Where(s => s.StructureType.Equals(StructureType.ProjectModelHierarchy)).Select(a => a.Uri).ToArray(), true);
			var rootAreaPaths = areaPathsXml.ChildNodes.Cast<XmlNode>().Where(a => a.FirstChild != null).SelectMany(a => a.FirstChild.ChildNodes.Cast<XmlNode>().SelectMany(c => ParseAreaPathFromNodes(c)));

			return ExtractAllAreaPaths(rootAreaPaths).ToArray();
		}

		private IEnumerable<AreaPath> RequestAllAreaPathsByProject(string projectName)
		{
			var css = TfsConnection.GetService<ICommonStructureService3>();
			var allStructures = css.ListStructures(css.GetProjectFromName(projectName).Uri);
			var areaPathsXml = css.GetNodesXml(allStructures.Where(s => s.StructureType.Equals(StructureType.ProjectModelHierarchy)).Select(a => a.Uri).ToArray(), true);
			var rootAreaPaths = areaPathsXml.ChildNodes.Cast<XmlNode>().Where(a => a.FirstChild != null).SelectMany(a => a.FirstChild.ChildNodes.Cast<XmlNode>().SelectMany(c => ParseAreaPathFromNodes(c)));

			return ExtractAllAreaPaths(rootAreaPaths).ToArray();
		}

		private IEnumerable<AreaPath> RequestSubAreas(string rootAreaName)
		{
			ICommonStructureService3 css = TfsConnection.GetService<ICommonStructureService3>();
			IEnumerable<NodeInfo> allStructures = css.ListAllProjects().SelectMany(p => css.ListStructures(p.Uri));
			XmlElement areaPathsXml = css.GetNodesXml(allStructures.Where(s => s.StructureType.Equals(StructureType.ProjectModelHierarchy)).Select(a => a.Uri).ToArray(), true);
			List<AreaPath> areas = ExtractAllAreaPaths(areaPathsXml.ChildNodes.Cast<XmlNode>()
				.Where(a => a.FirstChild != null)
				.SelectMany(a => a.FirstChild.ChildNodes.Cast<XmlNode>()
									.SelectMany(c => ParseAreaPathFromNodes(c))));

			string encodedPath = EntityTranslator.EncodePath(string.Format(CultureInfo.InvariantCulture, "{0}\\", rootAreaName.TrimEnd('\\')));

			if (areas.SingleOrDefault(a => a.Path.Equals(rootAreaName.TrimEnd('\\'))) == null)
			{
				throw new DataServiceException(404, "Not Found", string.Format(CultureInfo.InvariantCulture, "The AreaPath specified could not be found: {0}", rootAreaName), "en-US", null);
			}

			return areas.Where(a => a.Path.StartsWith(encodedPath, StringComparison.OrdinalIgnoreCase)).ToArray();
		}

		private static List<AreaPath> ExtractAllAreaPaths(IEnumerable<AreaPath> areas)
		{
			var allAreas = new List<AreaPath>();
			if (areas != null)
			{
				allAreas.AddRange(areas.SelectMany(a => ExtractAllAreaPaths(a.SubAreas)));
				allAreas.AddRange(areas);
			}

			return allAreas;
		}

		private IEnumerable<AreaPath> ParseAreaPathFromNodes(XmlNode currentNode)
		{
			var results = new List<AreaPath>();
			var subAreas = default(IEnumerable<AreaPath>);

			if (currentNode.ChildNodes != null)
			{
				var childrenNode = currentNode.ChildNodes.Cast<XmlNode>().SingleOrDefault(n => n.Name.Equals("Children"));
				if (childrenNode != null)
				{
					subAreas = childrenNode.ChildNodes.Cast<XmlNode>().SelectMany(n => ParseAreaPathFromNodes(n));
				}
			}

			if (currentNode.Attributes["Name"] != null && currentNode.Attributes["Path"] != null)
			{
				results.Add(currentNode.ToModel(subAreas));
			}

			return results;
		}

		private string GetAllAreaPathsKey()
		{
			return string.Format(CultureInfo.InvariantCulture, "TFSAreaPathProxy.GetAllAreaPaths");
		}

		private string GetAreaPathsByProjectKey(string projectName)
		{
			return string.Format(CultureInfo.InvariantCulture, "TFSAreaPathProxy.GetAreaPathByProject_{0}", projectName);
		}
	}
}
